/*
 * Created on 2010-12-09
 * The main class to generate messages.
 */
package algo;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;

import entity.AlertRegion;
import entity.Cell;
import entity.Field;
import entity.MovingObject;
import entity.Statistics;

/**
 * 2. test MyEventQ.java [done] <br>
 * 3. test message generator [done] <br>
 * 4. check writeo2s message, duplicate time points[done]<br>
 * 5. check the number of messages (incoming and outgoing) are correct<br>
 * 6. Circle implementation is incorrect. correct it.[done] <br>
 * 7. check eventQ is correct<br>
 * 8. change velocity to speed, which was read from data sets<br>
 * 9. create multiple servers and count the messages for each server<br>
 * 10. result change rate
 * 
 * @author Xiaohui
 * 
 */
public class MGSimple {
	static int cellLength;
	double maxWidth = 0;
	double maxHeight = 0;
	static int currentTS;
	boolean end;
	Field field;
	String[] lastORecord;
	// int numQueries;
	HashMap<Integer, MovingObject> objects;
	// HashMap<Integer, MovingObject> q_objects;

	static String ofilePath;
	private static int numObj;
	File o2sMsgFilePath;
	BufferedReader obr;
	// BufferedWriter o2sMsgBw;// write out o2s messages.
	// BufferedWriter s2oMsgBw;// write out s2o messages.

	Statistics stat;
	String speedTag;
	List<Integer> enterNewCellMo;
	List<Integer> changeLocationMo;
	private static int qRadius;

	// private File s2oMsgFilePath;

	/**
	 * Find query result for currmo
	 * 
	 * @param currmo
	 */
	void findResult(MovingObject currmo) {
		ArrayList<Integer> oldResult = null;
		if (currentTS != 0) {
			oldResult = new ArrayList<Integer>(currmo.currResult);
		}

		// for each overlapping cell,if these mo satisfy the query, add
		// registered mo into result
		MovingObject tempmo;
		for (Cell c : currmo.alertRegion.getRegion()) {
			// handle full cell
			// add all objects in these cells
			if (currmo.contains(c)) {
				currmo.currResult.addAll(c.registeredObjects);
				currmo.currResult.remove(new Integer(currmo.oid));
				// add qo into every object's result
				for (int k : c.registeredObjects) {
					if (k != currmo.oid) {
						tempmo = this.objects.get(k);
						tempmo.addSatisfiedQuery(currmo.oid);
					}
				}
			} else {
				// handle partial cells
				for (int k : c.registeredObjects) {
					tempmo = this.objects.get(k);
					// mo is in the result set or
					// mo is put into the event list
					if (currmo.contains(tempmo.getCenterP())
							&& currmo.oid != tempmo.oid) {
						currmo.addResultObject(k);
						tempmo.addSatisfiedQuery(currmo.oid);
					}
				}
			}
		}
		// compare the diff between old results and new results
		if (oldResult != null) {
			Collection c1 = CollectionUtils.intersection(oldResult,
					currmo.currResult);
			Collection c2 = CollectionUtils.union(oldResult, currmo.currResult);
			stat.changedResult += (c2.size() - c1.size());
		}
	}

	public String[] getOneObjectRecord() {
		String line = null;
		try {
			line = this.obr.readLine();
			if (line != null) {
				return line.split("\t");
			} else {
				this.end = true;
				return null;
			}
		} catch (IOException e) {
			System.out.println("in time0process, IOException");
			System.exit(-1);
		}
		return null;
	}

	// public String[] getOneQueryRecord() {
	// String line = null;
	// try {
	// line = this.qbr.readLine();
	// if (line != null) {
	// return line.split("\t");
	// } else {
	// return null;
	// }
	// } catch (IOException e) {
	// System.out.println("in time0process, IOException");
	// System.exit(-1);
	// }
	// return null;
	// }

	public MovingObject getObject(int oid) {
		return this.objects.get(oid);
	}

	// public MovingObject getQ_object(int oid) {
	// return this.q_objects.get(oid);
	// }

	/**
	 * When a cell moves from a cell (pre) to a new cell (curr), <br>
	 * 1) the moving object sends its current location and request for list of
	 * queries in the curr cell <br>
	 * 2) for each q \in list of queries <br>
	 * if mo satisfies q <br>
	 * add mo to q <br>
	 * 
	 * @throws Exception
	 */

	private void handleEnterCell(MovingObject mo, Cell prevCell, Cell currCell,
			ArrayList<Integer> queries2inform) throws Exception {
		// remove mo from prevCell, add to currCell
		prevCell.registeredObjects.remove(new Integer(mo.oid));
		currCell.registeredObjects.add(mo.oid);

		// generate messages
		// Msg_o2s_enterCell mec = new Msg_o2s_enterCell(this.currentTS, 0,
		// mo.oid, prevCell.getCellId(), currCell.getCellId());

		// no need s2o, because it is location update
		// this.putMsgIntoFile(currentTS, mqr.toString(), false);

		if (currCell.registeredQueries != null) {
			MovingObject q;
			for (Integer i : currCell.registeredQueries) {
				// Msg_s2o_enterAR mar = new Msg_s2o_enterAR(currentTS);
				// this.putMsgIntoFile(currentTS, mar.toString(), false);
				if (queries2inform.contains(i)) {
					queries2inform.add(i);
				}

				// real work here
				q = this.objects.get(i);
				if (q.contains(mo.getCenterP()) && q.oid != mo.oid) {
					q.addResultObject(mo.oid);
					mo.addSatisfiedQuery(i);
				}

				if (mo.contains(q.getCenterP()) && q.oid != mo.oid) {
					mo.currResult.add(q.oid);
					q.addSatisfiedQuery(mo.oid);
				}
			}
		}
	}

	/**
	 * 
	 * update the results; differential result from pre-move to post-move<br>
	 * no message is sent unless currmo result changes<br>
	 * 
	 * @param mo
	 * @throws Exception
	 */
	void handleFocalMove(MovingObject mo) throws Exception {
		// copy original result
		ArrayList<Integer> oldResult = new ArrayList<Integer>();
		Collections.copy(mo.currResult, oldResult);

		mo.alertRegion.setRegion(field.intersectWith(mo));
		// clear current result
		for (int i : mo.currResult) {
			this.objects.get(i).satisfiedQueries.remove(new Integer(mo.oid));
		}
		mo.currResult.clear();

		// boolean sendMsg = false;
		findResult(mo);

		boolean sendCanResult = false;
		for (Integer i : mo.currResult) {
			if (!oldResult.contains(i)) {
				sendCanResult = true;
				break;
			}
		}
		if (sendCanResult) {
			this.stat.s2oCanResultMsgs++;
		}
		// Msg_o2s_focalMove o2smfm = new Msg_o2s_focalMove(currentTS, mo.oid,
		// mo.getCenterP());
		// Msg_s2o_focalMove s2omfm = new Msg_s2o_focalMove(currentTS);
		// this.putMsgIntoFile(currentTS, o2smfm.toString(), true);
		// this.putMsgIntoFile(currentTS, s2omfm.toString(), false);
	}

	/**
	 * Every moving client maintains a trigger, and it knows which mo exit
	 * current query circle. <br>
	 * 
	 * check if a moving object enters or exits a query circle <br>
	 * if true, in Simple implementation, messages should be sent <br>
	 * 
	 * @param queries2inform
	 * 
	 * @throws Exception
	 */
	private void handleObjMoveWithinSameCell(MovingObject mo,
			ArrayList<Integer> queries2inform) throws Exception {
		MovingObject tempMo;

		// check exit
		// function by trigger time, no message sent
		// Msg_o2s_objMove meq = new Msg_o2s_objMove(this.currentTS);
		// this.putMsgIntoFile(currentTS, meq.toString(), true);

		for (int i = 0; i < mo.satisfiedQueries.size(); i++) {
			tempMo = objects.get(i);
			if (!tempMo.isResult(mo)) {
				tempMo.currResult.remove(new Integer(mo.oid));
				mo.satisfiedQueries.remove(tempMo.oid);
				// Msg_s2o_exitQuery meqs2o = new Msg_s2o_exitQuery();
				// this.putMsgIntoFile(currentTS, meqs2o.toString(), false);
			}
		}

		// check enter
		// if true, add i into queries to which message is sent
		if (mo.currC.registeredQueries != null) {
			for (Integer i : mo.currC.registeredQueries) {
				tempMo = objects.get(i);
				if (!tempMo.currResult.contains(mo.oid)
						&& tempMo.contains(mo.getCenterP())) {
					tempMo.currResult.add(mo.oid);
					mo.satisfiedQueries.add(tempMo.oid);

					if (!queries2inform.contains(i)) {
						queries2inform.add(i);
					}
				}
			}
		}
	}

	/**
	 * Server initial processing
	 * 
	 * @throws Exception
	 */
	public void init(String o2sMsgFileName, String s2oMsgFileName,
			String statsFileName) throws Exception {
		this.currentTS = 0;
		// this.prepareMsgFile(o2sMsgFileName, s2oMsgFileName);
		System.out.println("Load data... ");
		this.loadObjectsTime0();
		System.out.println("Initial Process...");
		this.initialProcess();

		// for (int k : objects.keySet()) {
		// MovingObject mo = objects.get(k);
		// System.out.println("mo" + k + ": " + mo.currResult + "; "
		// + mo.satisfiedQueries);
		// }

		// System.out.println("query and cell relation: ");

		// continuous processing
		while (!this.end) {
			this.currentTS++;
			System.out.println("ts: " + this.currentTS);
			// read t_(i+1) data at time t_i and
			// check whose velocity change
			this.loadObjectsCurrTime();

			// update result
			long t1 = System.currentTimeMillis();
			this.processCurrentTime();
			long t2 = System.currentTimeMillis();
			this.stat.serverWorkLoad += (t2 - t1);

			// this.sampleObjectsWorkload();

			// System.out.println("ts:" + this.currentTS);
			// for (int k : objects.keySet()) {
			// MovingObject mo = objects.get(k);
			//
			// System.out.println(k + ": " + mo.currResult + "; "
			// + mo.satisfiedQueries);
			// }
		}
		String outString = "******MCQNMR*******\n" + "o dataset: " + ofilePath
				+ "\n" + "Field:" + maxWidth + "x" + maxHeight + "\n"
				+ "# of objects:" + numObj + "\n" + "Query radius: " + qRadius
				+ "\n" + "Cell Length: " + cellLength + "\n" + "speed tag: "
				+ speedTag + "\n" + "#o2s update msgs: " + stat.o2sMessages
				+ "\n" + "#s2o probe msgs: " + stat.s2oProbeMessages + "\n"
				+ "#s2o candidate msgs : " + stat.s2oCanResultMsgs + "\n"
				+ "server workload(s): " + stat.getServerWorkLoadSec() + "\n"
				+ "client msg rate: "
				+ stat.getClientMsgRate(currentTS, numObj) + "\n"
				+ stat.getAVGResultChange(numObj, currentTS);
		this.writeOutStatistics(statsFileName, outString);
		this.printStatisticsToScreen(outString);

	}

	/**
	 * 
	 * Compute initial results for the queries <br>
	 * go by query; retrieve objects in overlapping cells <br>
	 * send appropriate messages <br>
	 * 
	 * @throws Exception
	 */
	public void initialProcess() throws Exception {
		// for each query
		int counter = 0;
		MovingObject currmo;
		for (Integer i : this.objects.keySet()) {
			// System.out.println("Init query " + i);
			currmo = (MovingObject) this.objects.get(i);

			// // o2s to install queries
			// Msg_o2s_installQuery miq = new
			// Msg_o2s_installQuery(this.currentTS,
			// currmo.oid, currmo.getCenterP(), currmo.speed, currmo.currC);
			// // System.out.println(miq.toString());
			// this.putMsgIntoFile(this.currentTS, miq.toString(), true);

			// install query here
			this.stat.o2sMessages++;

			// s2o results messages
			// Msg_s2o_queryResult miqs2o = new Msg_s2o_queryResult(currentTS);
			// this.putMsgIntoFile(this.currentTS, miqs2o.toString(), false);

			long t1 = System.currentTimeMillis();
			findResult(currmo);
			// server to client query result
			this.stat.s2oCanResultMsgs++;
			long t2 = System.currentTimeMillis();
			this.stat.serverWorkLoad += (t2 - t1);

			counter++;
			if (counter % 1000 == 0) {
				System.err.println("Now " + counter);
			}
			// if (currmo.currResult.size() > 0) {
			// System.out.println(currmo.oid + " friends: "
			// + currmo.currResult.size());
			// }
			// //handle full cell
			// // handle partial cells
			// for (Cell c : currmo.partialCells) {
			// for (int k : c.registeredObjects) {
			// tempmo = this.objects.get(k);
			// // mo is in the result set or
			// // mo is put into the event list
			// if (currmo.contains(tempmo.getCenterP())
			// && currmo.oid != tempmo.oid) {
			// // do not add itself
			// currmo.addResultObject(k);
			// tempmo.addSatisfiedQuery(i);
			// }
			// }
			// }
			// remove itself from the result because of the addAll in full cell
			// currmo.currResult.remove(currmo.oid);
			// currmo.satisfiedQueries.remove(currmo.oid);

		}
	}

	/**
	 * 
	 * 1. Update moving objects' location and velocity. <br>
	 * 2. record which objects should be treated <br>
	 */
	public void loadObjectsCurrTime() {
		// clear the enter new cell first
		enterNewCellMo.clear();
		changeLocationMo.clear();
		// query objects
		MovingObject currmo;
		Point2D.Double tempP;
		String[] aORecord = this.lastORecord;
		int readTS = Integer.parseInt(aORecord[4]);
		while (aORecord != null && readTS == currentTS) {
			int qid = Integer.parseInt(aORecord[1]);
			double px = Double.parseDouble(aORecord[5]);
			double py = Double.parseDouble(aORecord[6]);
			double speed = Double.parseDouble(aORecord[7]);
			currmo = this.objects.get(qid);
			tempP = new Point2D.Double(px, py);

			// record migrate moving objects to handle at processCurrTime()
			if (!currmo.getCenterP().equals(tempP)) {
				changeLocationMo.add(currmo.oid);
			}

			// move the point to nextP
			move(currmo, speed, tempP);

			aORecord = this.getOneObjectRecord();
			if (aORecord != null) {
				readTS = Integer.parseInt(aORecord[4]);
			}
		}
		// the last record read in is for time step 1, so we need to keep it.
		this.lastORecord = aORecord;
	}

	/**
	 * Create moving objects and moving queries from the data.
	 */
	public void loadObjectsTime0() {
		String[] aORecord = this.getOneObjectRecord();
		assert (aORecord != null) : "in time0process, get null record";
		int readTS = Integer.parseInt(aORecord[4]); // time starts from 0
		while (aORecord != null && readTS == currentTS) {
			int oid = Integer.parseInt(aORecord[1]);
			double px = Double.parseDouble(aORecord[5]);
			double py = Double.parseDouble(aORecord[6]);
			if (px > maxWidth) {
				maxWidth = px;
			}
			if (py > maxHeight) {
				maxHeight = py;
			}

			Point2D p = new Point2D.Double(px, py);
			Cell cell = this.field.inWhichCell(p);
			double speed = Double.parseDouble(aORecord[7]);

			MovingObject mo = new MovingObject(oid, qRadius, p, speed,
					currentTS, cell, 0, this.field);

			this.objects.put(new Integer(mo.oid), mo);

			// cell register object here
			cell.registeredObjects.add(mo.oid);

			// cell register queries here
			ArrayList<Cell> affectedCells = field.intersectWith(mo);

			mo.setAlertRegion(new AlertRegion(affectedCells));

			for (Cell c : affectedCells) {
				c.addQuery(mo.oid);
			}
			// // query register full cell and partial cells
			// for (Cell c : affectedCells) {
			// if (mo.contains(c)) {
			// mo.addFullCells(c);
			// } else {
			// mo.addPartCells(c);
			// }
			// }

			aORecord = this.getOneObjectRecord();
			if (aORecord != null) {
				readTS = Integer.parseInt(aORecord[4]);
			}
		}
		// the last record read in is for time step 1, so we need to keep it.
		this.lastORecord = aORecord;
	}

	public MGSimple(int numCellsPerRow, int numCellsPerCol, String speedtag,
			String o2sMsgFileName, String s2oMsgFileName, String statsFileName)
			throws Exception {
		this.field = new Field(numCellsPerRow, numCellsPerCol, cellLength);
		this.objects = new HashMap<Integer, MovingObject>(numObj);
		// this.queryTable = new HashMap<Integer, QueryTableEntry>();

		this.speedTag = speedtag;

		this.stat = new Statistics();
		this.end = false;
		this.enterNewCellMo = new ArrayList<Integer>();
		this.changeLocationMo = new ArrayList<Integer>();

		// file handle
		FileReader ofr = new FileReader(ofilePath);
		this.obr = new BufferedReader(ofr);

		this.init(o2sMsgFileName, s2oMsgFileName, statsFileName);
	}

	public static void main(String[] args) throws Exception {

		// String q_Oldenburg_5k_slow = "D:/Research/data_gen/dismknn/q10k.txt";

		/**
		 * field conf
		 */
		cellLength = 40;
		int olden_numCellsRow = 600;
		int olden_numCellsCol = 800;

		if (args.length != 3) {
			System.out.println("usage: o_dataset numObj qRadius");
			System.exit(0);
		} else {
			ofilePath = args[0];
			numObj = Integer.parseInt(args[1]);
			qRadius = Integer.parseInt(args[2]);
			//
			// o_dataset = args[0];
			// oldenCellLength = Integer.parseInt(args[1]);
			// speedTag = args[2];
		}

		/**
		 * output files
		 */
		String prog = "MCQNOSR";
		String sub = ofilePath.substring(0, ofilePath.indexOf(".")) + prog;
		String olden_o2sMsgFile = sub + "_" + qRadius + "_" + cellLength
				+ "_O2Smsgs.txt";
		String olden_s2oMsgFile = sub + "_" + qRadius + "_" + cellLength
				+ "_S2Omsgs.txt";
		String olden_statsFile = sub + "_" + qRadius + "_" + cellLength
				+ "_nosrstats.txt";

		// test parameters (10x10 grids)
		String testData = "E:/Research/MCQ/LSMCQ/expt/test3mo.txt";
		String testMsgFile = "E:/Research/MCQ/LSMCQ/expt/test_msgs.txt";
		int test_CellLength = 1;
		int test_numCellsRow = 10;
		int test_numCellsCol = 10;
		int test_radius = 3;
		int test_numObj = 3;

		String speedTag = "slow";

		// System.exit(-1);
		// MessageGenerator mg = new MessageGenerator(olden_numCellsRow,
		// olden_numCellsCol, oldenCellLength, o_dataset, olden_radius,
		// speedTag);

		// MGSimple mg1 = new MGSimple(test_numObj, test_numCellsRow,
		// test_numCellsCol, test_CellLength, testData, test_radius,
		// speedTag, testMsgFile);
		//
		System.out.println("*****MCQNOSR*******");
		long t1 = System.currentTimeMillis();
		MGSimple mg_real = new MGSimple(olden_numCellsRow, olden_numCellsCol,
				speedTag, olden_o2sMsgFile, olden_s2oMsgFile, olden_statsFile);
		long t2 = System.currentTimeMillis();
		System.out.println("MGSimple Time Elapsed(s): " + (t2 - t1) / 1000.0);

	}

	/**
	 * move a moving object to a new location nextP
	 * 
	 * @param nextP
	 * 
	 */
	void move(MovingObject currmo, double speed, Point2D nextP) {
		currmo.prevP = currmo.getCenterP();
		currmo.speed = speed;
		currmo.setCenter(nextP);
		currmo.currC = field.inWhichCell(nextP);
	}

	// void prepareMsgFile() {
	// // prepare msg output file
	// String msgFileName = "o2sMsgs_o" + this.numObjects + "_c"
	// + this.cellLength + "_" + this.speedTag + ".txt";
	// this.o2sMsgFilePath = new File(msgFileName);
	// this.s2oMsgFilePath = new File(s2oMsgFileName);
	// try {
	// FileWriter o2sFw = new FileWriter(this.o2sMsgFilePath);
	//
	// this.o2sMsgBw = new BufferedWriter(o2sFw);
	// this.o2sMsgBw = new BufferedWriter(s2oFw);
	//
	// } catch (IOException e) {
	// System.out.println("In MessageGenerator(), IO exception");
	// System.exit(-1);
	// }
	// }

	// void prepareMsgFile(String o2sMsgFileName, String s2oMsgFileName) {
	// // prepare msg output file
	//
	// this.o2sMsgFilePath = new File(o2sMsgFileName);
	// this.s2oMsgFilePath = new File(s2oMsgFileName);
	// try {
	// FileWriter o2sFw = new FileWriter(this.o2sMsgFilePath);
	// FileWriter s2oFw = new FileWriter(this.s2oMsgFilePath);
	// this.o2sMsgBw = new BufferedWriter(o2sFw);
	// this.s2oMsgBw = new BufferedWriter(s2oFw);
	// } catch (IOException e) {
	// System.out.println("In MessageGenerator(), IO exception");
	// System.exit(-1);
	// }
	// }

	/**
	 * public void writeOutCells() { String cellFileName = "cellConfig_" +
	 * this.cellR + ".txt"; this.cellOutPath = new File(cellFileName); try {
	 * FileWriter cellFw = new FileWriter(this.cellOutPath); this.cbw = new
	 * BufferedWriter(cellFw); } catch (IOException e) {
	 * System.out.println("In MessageGenerator(), IO exception");
	 * System.exit(-1); }
	 * 
	 * for (int r = 1; r <= this.field.CMatrix.numRows; r++) { CellRow cr =
	 * this.field.CMatrix.getRow(r); for (int c = 1; c < cr.cellColumns.size();
	 * c++) { Cell cell = (Cell) cr.cellColumns.get(c); String ps = cell.cid.row
	 * + " " + cell.cid.column + " " + cell.center.x + " " + cell.center.y + " "
	 * + cell.sideLength; try { this.cbw.write(ps + "\n"); } catch (IOException
	 * e) { System.out.println("In printAllObjects(), IO exception");
	 * System.exit(-1); } } }
	 * 
	 * try { this.cbw.flush(); } catch (IOException e) {
	 * System.out.println("In convert(), IO exception"); System.exit(-1); }
	 * 
	 * }
	 **/
	/**
	 * @return a String array representing a record; null when come to the end
	 *         of the file. read a record from the input file.
	 */

	static void parseArgs(String[] args) {
		if (args.length > 0) {
			if (args.length != 5) {
				System.out.println("usage: o_dataset cellLength speedTag");
			} else {
				// o_dataset = args[0];
				// oldenCellLength = Integer.parseInt(args[1]);
				// speedTag = args[2];
			}
		}
	}

	void parseLine(String line) {
		String[] subStrings = line.split("\t");
		// System.out.println(subStrings.length);
		for (int i = 0; i < subStrings.length; i++) {
			System.out.println(subStrings[i]);
		}
		if (subStrings.length == 10) {
			String recordTyppe = subStrings[0];
			System.out.println(recordTyppe);

			if (recordTyppe.equals("newpoint")) {
				System.out.println("a new point is generated, with oid "
						+ subStrings[1]);

			} else if (recordTyppe.equals("point")) {
				System.out.println("point " + subStrings[1] + " moves");

			} else if (recordTyppe.equals("disappearpoint")) {
				System.out.println("point " + subStrings[1] + " disappears");
			}
		}
	}

	/**
	 * Essential method to handle object movement for each query, update its
	 * result <br>
	 * 
	 * @throws Exception
	 * 
	 */
	public void processCurrentTime() throws Exception {
		// mo moves
		MovingObject currmo;

		ArrayList<Integer> queries2inform = new ArrayList<Integer>();

		// mq moves
		// 1. examine monitoring moving objects
		// 2. examine new cell's moving object
		for (int i : changeLocationMo) {

			currmo = this.objects.get(i);
			// send to server about new location
			this.stat.o2sMessages++;

			// moving object procedure
			// case 1. as a moving object, it moves to a new cell
			if (!field.inWhichCell(currmo.getCenterP()).equals(
					field.inWhichCell(currmo.prevP))) {
				// it enters a new cell
				this.handleEnterCell(currmo, field.inWhichCell(currmo.prevP),
						field.inWhichCell(currmo.getCenterP()), queries2inform);
			}
			// case 2. as a moving object, it does not enter a new cell
			else {
				handleObjMoveWithinSameCell(currmo, queries2inform);
			}

			this.stat.s2oCanResultMsgs += queries2inform.size();

			// moving query procedure
			// send msg if result is updated
			handleFocalMove(currmo);
		}

	}

	/**
	 * 
	 * @param msgTime
	 * @param msgString
	 * @param incoming
	 *            : true if o2s, else false //
	 */
	// void putMsgIntoFile(int msgTime, String msgString, boolean incoming)
	// throws Exception {
	// // int key = msgTime;
	// // System.out.println("msg time; " + msgTime);
	// // System.out.println(this.timeMsgMap_o2s.keySet());
	// // if (this.timeMsgMap_o2s.containsKey(msgTime)) {
	// // msgList = this.timeMsgMap_o2s.get(key);
	// // msgList.add(msgString);
	// // } else {
	// // msgList = new ArrayList<String>();
	// // msgList.add(msgString);
	// // }
	// if (incoming) {
	// // this.o2sMsgBw.write(msgString);
	//
	// // this.timeMsgMap_o2s.add(msgString);
	// this.stat.o2sMessages++;
	// } else {
	// // this.s2oMsgBw.write(msgString);
	// // this.timeMsgMap_s2o.add(msgString);
	// this.stat.s2oProbeMessages++;
	// }
	// }

	//
	// public void processNTS() {
	// for (int qid : q_objects.keySet()) {
	// // System.out.println(this.queryTable.get(qid));
	// double pre_event_time = 0;
	// Msg_o2s_criticalObjectChange event = this.aQuerysNextEvent(qid,
	// pre_event_time);
	// while (event != null) {
	// // System.out.println(event);
	// this.putMsgIntoMap(event.timepoint, event.toString());
	//
	// event = this.aQuerysNextEvent(qid, event.timepoint
	// - this.currentTS);
	// }
	// }
	// }

	/**
	 * @param qid
	 * @param pre_event_time
	 * @return -1 if no critical object event will happen between pre_event_time
	 *         and 1; otherwise the time point of this event. Note that the
	 *         event is processed in this method.
	 */
	// public Msg_o2s_criticalObjectChange aQuerysNextEvent(int qid,
	// double pre_event_time) {
	// Msg_o2s_criticalObjectChange mcoc = null;
	// QueryTableEntry qte = (QueryTableEntry) this.queryTable
	// .get(new Integer(qid));
	// int co_id = qte.criticalOid;
	// double earlist = 1.00001;
	// int causingOid = 0;// todo: is it ok to init it as 0?
	// for (CellID aCid : qte.affectedCells) {
	// Cell aCell = this.getCell(aCid);
	// for (int oid : aCell.registeredObjects) {
	// if (oid != co_id) {
	// double eventTime = this.estimateCriticalChangeTime(qid,
	// co_id, oid, pre_event_time);
	// if (eventTime != -1 && eventTime > pre_event_time + 0.00001) {
	// if (eventTime < earlist) {
	// earlist = eventTime;
	// causingOid = oid;
	// } else if (eventTime == earlist) {
	// System.err.println("simultaneous events detected");
	// }
	// }
	// }
	// }
	// }
	// if there is an event, process
	// if (earlist < 1.00001) {
	// MovingObject causingMO = this.getObject(causingOid);
	// // calculate the position of the causingMO at this time
	// Location eventLoc = causingMO.locationAfterTime(earlist);
	// // TODOne: o2s critical change message
	// mcoc = new Msg_o2s_criticalObjectChange(this.currentTS + earlist,
	// qid, causingOid, eventLoc, causingMO.v, co_id);
	// }
	// return mcoc;
	// }

	// /**
	// * update objects' cells.
	// */
	// public void postProcessNTS_O() {
	// // todo: what is done here?
	// // todo: bug: cell is updated only at the end of a time unit
	// for (Integer i : objects.keySet()) {
	// MovingObject mo = (MovingObject) this.objects.get(i);
	// if (mo.nextP != null) {
	// CellID cid = this.field.inWhichCell(mo.nextP);
	// if (!mo.registeredCell.equals(cid)) {
	// // un-register the mo from previsous cell
	// CellID pre_cid = mo.registeredCell;
	// Cell pre_cell = this.getCell(pre_cid);
	// pre_cell.unregister(mo.oid);
	//
	// // test whether fly happens
	// assert (pre_cell.neighboringCells.contains(cid)) :
	// "in postProcessNTS_O, the object flies into a non-neighboring cell";
	// // register it to the new cell
	// Cell cur_cell = this.getCell(cid);
	// cur_cell.register(mo.oid);
	// // delta queries, minus, plus
	// mo.registeredCell = cid;
	// // TODOne: o2s cell change message
	// int fromObject = 0;
	// Msg_o2s_enterCell mec = new Msg_o2s_enterCell(
	// this.currentTS + 1, fromObject, mo.oid, pre_cid,
	// cid);
	// this.putMsgIntoMap((this.currentTS + 1), mec.toString());
	// this.handleEnterCell(mo.oid, pre_cid, cid);
	// }
	// mo.currP = mo.nextP;
	// }
	// }
	// }

	// /**
	// * update queries' cells.
	// */
	// public void postProcessNTS_Q() {
	// for (QueryTableEntry qte : this.queryTable.values()) {
	// int qoid = qte.focalOid;
	// MovingObject qmo = this.getQ_object(qoid);
	// if (qmo.nextP != null) {
	// CellID cid = this.field.inWhichCell(qmo.nextP);
	// if (!qmo.registeredCell.equals(cid)) {
	// CellID pre_cid = qmo.registeredCell;
	// int fromQuery = 1;
	// Msg_o2s_enterCell mec = new Msg_o2s_enterCell(
	// this.currentTS + 1, fromQuery, qmo.oid, pre_cid,
	// cid);
	// this.putMsgIntoMap((this.currentTS + 1), mec.toString());
	// qmo.registeredCell = cid;
	// }
	// qmo.currP = qmo.nextP;
	// }
	// }
	// }

	// public void NTS() {
	// this.processNTS();
	// this.postProcessNTS_O();
	// this.postProcessNTS_Q();
	// this.currentTS++;
	// this.writeOutO2sMsgs();
	// this.sampleObjectsWorkload();
	// }

	// public void continuousNTS() {
	// while (!this.end) {
	// this.NTS();
	// System.err.println("ts:" + this.currentTS);
	// }
	// this.stat.calculateStatistics(this.objects.size(),
	// this.q_objects.size(), this.currentTS);
	// this.writeOutStatistics();
	// this.printStatisticsToScreen();
	// }
	public void printStatisticsToScreen(String str) {
		System.out.println(str);
	}

	// /**
	// */
	// public void sampleObjectsWorkload() {
	// for (int i = 0; i < this.objects.size(); i++) {
	// MovingObject mo = this.getObject(i);
	// // this.stat.numQueriesByObjects = this.stat.numQueriesByObjects;
	// }
	// }

	// public void checkACellForNearerObjects(CellID cid, int qid,
	// Location focolLoc, boolean withCD, double distance,
	// DisObjsMap disObjMap) {
	// Cell cell = this.field.CMatrix.getCell(cid);
	// for (int oid : cell.registeredObjects) {
	// MovingObject mo = this.getObject(oid);
	// if (withCD) {
	// double nearerDis = mo.nearer(focolLoc, distance);
	// if (nearerDis > 0) {
	// disObjMap.put(nearerDis, oid);
	// // TODOne:o2s reply message
	// Msg_o2s_reply mr = new Msg_o2s_reply(this.currentTS, qid,
	// mo.oid, mo.currP, mo.v, mo.registeredCell);
	// this.putMsgIntoMap(this.currentTS, mr.toString());
	// }
	// } else {
	// double dis = mo.currP.distance(focolLoc);
	// if (dis == 0) {
	// System.err
	// .println("the object is at the same location as the query object");
	// }
	// if (disObjMap.containsKey(new Double(dis))) {
	// System.err
	// .println("objects with a same distance to the query object are found");
	// }
	// disObjMap.put(dis, oid);
	// // TODOne:o2s reply message
	// Msg_o2s_reply mr = new Msg_o2s_reply(this.currentTS, qid,
	// mo.oid, mo.currP, mo.v, mo.registeredCell);
	// this.putMsgIntoMap(this.currentTS, mr.toString());
	// }
	// }
	//
	// }

	// public void printObjectsOwnQuery() {
	// for (int i = 0; i < this.numObjects; i++) {
	// MovingObject mo = (MovingObject) this.objects.get(new Integer(i));
	// mo.printOwnQueries();
	// }
	// }

	public void printQT() {
		System.out.println("======================");
		System.out.println("==========QT==========, at time " + this.currentTS);
		// for (int i = 0; i < this.queryTable.size(); i++) {
		// QueryTableEntry qte = (QueryTableEntry) this.queryTable
		// .get(new Integer(i));
		// System.out.println(qte);
		// }
	}

	public void printObjects() {
		System.out.println("======================");
		System.out.println("==========Moving Objects==========, at time "
				+ this.currentTS);
		for (int i = 0; i < this.objects.size(); i++) {
			MovingObject mo = this.getObject(i);
			System.out.println(mo);
		}
	}

	public void printQueryObjects() {
		System.out.println("======================");
		System.out.println("==========Query Objects==========, at time "
				+ this.currentTS);
		for (int i = 0; i < this.objects.size(); i++) {
			MovingObject mo = this.objects.get(i);
			System.out.println(mo);
		}
	}

	// /**
	// * write out the o2s messages. This also makes the message queue empty.
	// */
	// public void writeOutO2sMsgs() {
	// System.out.println("num msg:" + timeMsgMap_o2s.size());
	//
	// for (String msg : timeMsgMap_o2s) {
	// try {
	// this.o2sMsgBw.write(msg + "\n");
	// } catch (IOException e) {
	// System.out.println("In writeOutO2sMsgs(), IO exception");
	// System.exit(-1);
	// }
	// }
	//
	// this.timeMsgMap_o2s.clear();
	//
	// try {
	// this.o2sMsgBw.flush();
	// } catch (IOException e) {
	// System.out.println("In writeOutO2sMsgs(), IO exception");
	// System.exit(-1);
	// }
	// }

	public void writeOutStatistics(String statsFileName, String outString)
			throws Exception {
		// write statistics out
		FileWriter stat_fw = new FileWriter(statsFileName);
		BufferedWriter sbw = new BufferedWriter(stat_fw);
		sbw.write(outString);
		sbw.newLine();
		sbw.close();

	}
}
